顔認証のクラウドサービスMercury Cloudでさらに遊んでみる – デバイスのカメラ映像を顔特徴データベースに登録 –
今回の記事では、ローカルのデバイス上のカメラの映像をMercury Cloudの顔特徴データベースに登録してみます。 顔認証をする際、カメラで撮った顔の映像をDB内で検索して本人かどうかを確かめるケースを想定しています。
カメラを扱う方法は色々とあると思いますが、今回はMedia Capture and StreamsというJavaScript APIを使ってHTML上で動作させる方法を使ってみます
※ クライアント、サーバー側ともjavascriptを使用しています
ローカルデバイスのカメラにアクセスする
getUserMedia() を使ってカメラ等のストリームを得ることができます。
例) カメラの映像をvideo要素に映す
const video = document.querySelector("#cameraStream"); navigator.mediaDevices.getUserMedia(constraints) .then( (stream) => { // 取得したストリームを使う処理 video.srcObject = stream; video.onloadedmetadata = (e) => { video.play(); }; }) .catch( (err) => { // エラー処理 console.log(err.name + ": " + err.message); });
getUserMediaの引数ですが、MediaStreamConstraintsオブジェクトです。
MediaStreamConstraintsのオブジェクトは、getUserMedia()を呼び出した時に返されるMediaStreamに含まれるトラックの種類が何であるかを知るため、また、これらのトラック設定の制約を確立するために使用されます
例) 音無しでフロントカメラを利用する場合のMediaStreamConstraintsオブジェクト
const constraints = { audio: false, video: { width: 300, height: 200, facingMode: "user" // フロントカメラを利用する // facingMode: { exact: "environment" } // リアカメラを利用する場合 } };
例に挙げたコードを実行すると、以下のような感じでHTMLでカメラの映像を表示できるようになりました。
htmlにアクセスしている端末にカメラがないと映像は表示されません。
カメラで静止画を取得する
カメラの映像が表示されたので、次はボタンを押したらその時の状態を取得してみます。
HTMLにcanvas要素を置いて、そこに転写することができます。
const canvas = document.querySelector("#picture"); // ボタンを押した時の操作 document.querySelector("#copying").addEventListener("click", () => { const ctx = canvas.getContext("2d"); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); });
canvasのdrawImageメソッドを使用することで元イメージを描画することができます。
上がビデオの映像で、下がボタンを押した時のビデオ映像をカンバスにコピーした画像となっています。
base64でデータを取得
最終的にMercury CloudのAPIにPOSTするのですが、一旦サーバープログラムの方にPOSTします。
カメラから撮影した画像をbase64でデータ化して送ってみましょう。
先程のcanvasのtoDataURL()
メソッドを使うと取得できるようになります
const canvas = document.querySelector("#picture"); // ボタンを押した時の操作 document.querySelector("#copying").addEventListener("click", () => { const ctx = canvas.getContext("2d"); ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const base64_data = canvas.toDataURL() console.log(base64_data.replace(/^.*,/, '')) });
取得できました。
サーバー側に送信するときはbase64のままだったり、Blobに変換したりして送信できますが、今回はそのままbase64のまま送信することにしました。
例)
XMLHttpRequestのsend()メソッドを使った例です
xhr = new XMLHttpRequest(); xhr.open('POST', '/camera-post', true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); var request = "img=" + base64_data.replace(/^.*,/, '') xhr.onreadystatechange = function() { // 状態が変化すると関数が呼び出されます。 if (this.readyState === XMLHttpRequest.DONE && this.status === 200) { // リクエストの終了。ここの処理を実行します。 } } xhr.send(request);
顔特徴データベースに登録
サーバー側からPOSTされたbase64のデータを顔特徴データベースAPIにPOSTします。
Fastifyというフレームワークを使用した時の例)
特徴データベースに登録する前に品質チェックAPIを通した方が良いのですが、 今回は検証ということで割愛します。
fastify.post("/camera-post", function(request, reply) { const img_data = request.body.img.replace(/\s/g, '+') // POSTされてきたbase64のデータ // 特徴データベースに登録するデータ const data = JSON.stringify({ "image": { "data": img_data, "key":"Alice", "quality_threshold": 0.9 } }); const config = { method: 'post', url: 'https://<<api_url>>/openapi/face/v1/<<app_id>>/databases/<<db_id>>/features', headers: { 'x-date': x_date_time_string(), // 処理の中身は省略 'Authorization': authorization(), // 処理の中身は省略 'Content-Type': 'application/json' }, data : data }; axios(config) .then(response => { let res = response.data console.log(response.data) reply .code(200) .header('Content-Type', 'application/json; charset=utf-8') .send({ data: params}) }).catch(error => { console.log("error") reply .code(xxx) .header('Content-Type', 'application/json; charset=utf-8') .send({ data: params}) }) });
顔が検出されなかったら、
{ trace_id: '4e18a6dfef9ed46f64adacacfd186088', results: [ { code: 80303, message: 'No face found.', internal_code: 303 } ], features: [ { feature_id: '', face: null } ] }
検出されたら
{ trace_id: 'a392f9c14a5db269377f7d91fc189e59', results: [ { code: 0, message: 'Success.', internal_code: 0 } ], features: [ { feature_id: '91309912cbcf4985be08552363ed60010000000000071bc1', face: [Object] } ] }
といった結果が返ってきました。
feature_id
があると顔特徴がデータベースに登録されたということになります。
最後に
htmlとjavascriptを使ってローカルのカメラで取得した画像を顔特徴データベースに登録しました。
次回は2要素認証としての顔認証で、登録した特徴を元に顔検索を行うことを行なってみようかと思います。